#!/bin/sh /etc/rc.common
# Copyright (C) 2006-2011 OpenWrt.org

START=95

DEVCTL=/dev/qos_ctl
IPOPS="lua /usr/lib/lua/ipops.lua"
test -e /usr/share/natflow/ipops.lua && IPOPS="lua /usr/share/natflow/ipops.lua"

qos_idx=0

# ipset_add ipsetname net
ipv4set_add()
{
	local ipsetname=$1
	local net=$2
	#hack for 0.0.0.0/0
	[ "$net" = "0.0.0.0/0" ] && net="0.0.0.0/1 128.0.0.0/1"
	for n in $net; do
		ipset add $ipsetname $n
	done
}

get_rate_data()
{
	local cnt num unit
	echo -n $1 | grep -qi "bps$" || {
		num=$1
		echo -n $((num)) # assume num B/s
		return
	}
	cnt=`echo -n $1 | wc -c || echo 0`
	test $cnt -le 4 && echo -n 0 && return # assume 0 B/s

	num=`echo -n $1 | cut -c0-$((cnt-4))`
	unit=`echo -n $1 | cut -c$((cnt-3))-$cnt | tr A-Z a-z`
	case $unit in
		"kbps")
			num=$((num*128))
		;;
		"mbps")
			num=$((num*128*1024))
		;;
		"gbps")
			num=$((num*128*1024*1024))
		;;
		*)
			num=$((num/8))
		;;
	esac
	echo -n $num # assume num bps
}

natflow_qos_setup()
{
	local idx=$qos_idx
	qos_idx=$((qos_idx+1))
	local cfg="$1"
	local disabled user user_port remote remote_port proto rx_rate tx_rate

	config_get disabled "$cfg" disabled 0
	config_get user "$cfg" user
	config_get user_port "$cfg" user_port
	config_get remote "$cfg" remote
	config_get remote_port "$cfg" remote_port
	config_get proto "$cfg" proto
	config_get rx_rate "$cfg" rx_rate 0
	config_get tx_rate "$cfg" tx_rate 0

	[ "$disabled" = "1" ] && return 0

	#echo add user=<ipset/ip/ipcidr>,user_port=<portset/port>,remote=<ipset/ip/ipcidr>,remote_port=<portset/port>,proto=<tcp/udp>,rxbytes=Bytes,txbytes=Bytes

	user=$($IPOPS netStrings2ipcidrStrings "$user")
	if [ "$(echo $user | sed 's/,/ /g' | wc -w)" -gt 1 ]; then
		ipset create qos_u$idx nethash 2>/dev/null
		ipset flush qos_u$idx
		for net in $(echo $user | sed 's/,/ /g'); do
			ipv4set_add qos_u$idx $net
		done
		user=qos_u$idx
	fi

	if [ "$(echo $user_port | sed 's/,/ /g;s/-/ /g' | wc -w)" -gt 1 ]; then
		ipset create qos_up$idx bitmap:port range 0-65535 2>/dev/null
		ipset flush qos_up$idx
		for port in $(echo $user_port | sed 's/,/ /g'); do
			ipset add qos_up$idx $port
		done
		user_port=qos_up$idx
	fi

	remote=$($IPOPS netStrings2ipcidrStrings "$remote")
	if [ "$(echo $remote | sed 's/,/ /g' | wc -w)" -gt 1 ]; then
		ipset create qos_r$idx nethash 2>/dev/null
		ipset flush qos_r$idx
		for net in $(echo $remote | sed 's/,/ /g'); do
			ipv4set_add qos_r$idx $net
		done
		remote=qos_r$idx
	fi

	if [ "$(echo $remote_port | sed 's/,/ /g;s/-/ /g' | wc -w)" -gt 1 ]; then
		ipset create qos_rp$idx bitmap:port range 0-65535 2>/dev/null
		ipset flush qos_rp$idx
		for port in $(echo $remote_port | sed 's/,/ /g'); do
			ipset add qos_rp$idx $port
		done
		remote_port=qos_rp$idx
	fi

	rx_rate=$(get_rate_data "$rx_rate")
	tx_rate=$(get_rate_data "$tx_rate")

	cmd="add user=$user,user_port=$user_port,remote=$remote,remote_port=$remote_port,proto=$proto,rxbytes=$rx_rate,txbytes=$tx_rate"

	echo "$cmd" >$DEVCTL
}

natflow_qos_zone_setup_tc()
{
	local idx=$zone_idx
	zone_idx=$((zone_idx+1))
	local cfg="$1"
	local fw_zone ifname type

	config_get fw_zone "$cfg" fw_zone
	config_get ifname "$cfg" ifname
	config_get type "$cfg" type

	(for fwz in $fw_zone; do
		fw3 -q zone $fwz
	done; \
	for ifn in $ifname; do
		echo $ifn
	done) | sed 's/+$/\.\*/' | sort | uniq | while read IFN; do
		#echo $type $idx=$IFN >$DEVCTL
		case $type in
		lan_zone)
			ifconfig -a | grep "^$IFN " | awk '{print $1}' | while read lan; do
				if [ "${lan}" = "${lan//:}" ]; then
					qos_id=0
					cat /dev/qos_ctl | grep "^add user=" | while read line; do
						line="${line/*rxbytes=}"
						rxbytes="${line/,*}"
						quantum=$(($rxbytes/1000))
						if test $quantum -lt 256; then
							quantum=256
						fi
						qos_id=$((qos_id+1))
						QOSID=$((qos_id*2-1))
						lower1=$(for vif in /sys/class/net/$lan/lower_*; do test -e $vif && echo ${vif##/sys/class/net/*/lower_}; done)
						lower2=$(for dev in $lower1; do for vif in /sys/class/net/$dev/lower_*; do test -e $vif && echo ${vif##/sys/class/net/*/lower_}; done; done)
						devs=$(for dev in $lan $lower1 $lower2; do echo $dev; done | sort | uniq);
						echo setup tc for @lan=[`echo $devs`] rxbytes=$rxbytes quantum=$quantum rule_id=${qos_id} QOSID=${QOSID}
						for DEVICE in $devs; do
							tc qdisc add dev $DEVICE root handle 1: htb &>/dev/null
							tc class add dev $DEVICE parent 1: classid 1:${QOSID} htb rate ${rxbytes}Bps quantum $quantum &>/dev/null
							tc filter add dev $DEVICE parent 1: protocol all prio 1 handle ${QOSID} fw classid 1:${QOSID} &>/dev/null
						done
					done
				fi
			done
		;;
		wan_zone)
			ifconfig -a | grep "^$IFN " | awk '{print $1}' | while read wan; do
				if [ "${wan}" = "${wan//:}" ]; then
					qos_id=0
					cat /dev/qos_ctl | grep "^add user=" | while read line; do
						txbytes="${line/*,txbytes=}"
						quantum=$(($txbytes/1000))
						if test $quantum -lt 256; then
							quantum=256
						fi
						qos_id=$((qos_id+1))
						QOSID=$((qos_id*2))
						lower1=$(for vif in /sys/class/net/$wan/lower_*; do test -e $vif && echo ${vif##/sys/class/net/*/lower_}; done)
						lower2=$(for dev in $lower1; do for vif in /sys/class/net/$dev/lower_*; do test -e $vif && echo ${vif##/sys/class/net/*/lower_}; done; done)
						devs=$(for dev in $wan $lower1 $lower2; do echo $dev; done | sort | uniq);
						echo setup tc for @wan=[`echo $devs`] txbytes=$txbytes quantum=$quantum rule_id=${qos_id} QOSID=${QOSID}
						for DEVICE in $devs; do
							tc qdisc add dev $DEVICE root handle 1: htb &>/dev/null
							tc class add dev $DEVICE parent 1: classid 1:${QOSID} htb rate ${txbytes}Bps quantum $quantum &>/dev/null
							tc filter add dev $DEVICE parent 1: protocol all prio 1 handle ${QOSID} fw classid 1:${QOSID} &>/dev/null
						done
					done
				fi
			done
		;;
		esac
	done
}

natflow_qos_clear_tc()
{
	which tc &>/dev/null || return
	ifconfig | grep "^[^ ]" | awk '{print $1}' | while read ifname; do
		if [ "${ifname}" = "${ifname//:}" ]; then
			tc qdisc del dev $ifname root &>/dev/null
		fi
	done
}

start() {
	test -c $DEVCTL || return 0

	echo clear >$DEVCTL

	config_load natflow
	config_foreach natflow_qos_setup qos

	natflow_qos_clear_tc

	if [ "$(uci get natflow.main.tc_classid_mode 2>/dev/null || echo 0)" = "1" ] && which tc &>/dev/null; then
		echo tc_classid_mode=1 >/dev/qos_ctl
		config_foreach natflow_qos_zone_setup_tc zone
	else
		echo tc_classid_mode=0 >/dev/qos_ctl
	fi

	mkdir -p /tmp/config
	touch /tmp/config/natflow-qos
	ln -s /tmp/config/natflow-qos /etc/config/natflow-qos &>/dev/null
	config_load natflow-qos
	config_foreach natflow_qos_setup qos
}

stop() {
	test -c $DEVCTL || return 0

	echo clear >$DEVCTL
	ipset list -n | grep ^qos_ | while read ipset; do
		ipset destroy $ipset
	done

	natflow_qos_clear_tc
}

restart() {
	stop
	start
}
